<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8"/>
        <title>Design Patterns -  PHP 之道 | PHP The Right Way 中文版</title>
        <meta name="description" content=""/>
        <meta name="robots" content="index,follow,archive"/>
        <meta property="og:image:url" content="http://www.phptherightway.com/images/og-image.png"/>
        <meta property="og:image:width" content="1024"/>
        <meta property="og:image:height" content="640"/>
        <meta property="og:title" content="PHP: The Right Way"/>
        <meta property="og:description" content="An easy-to-read, quick reference for PHP best practices, accepted coding standards, and links to authoritative PHP tutorials around the Web"/>
        <meta property="og:url" content="http://www.phptherightway.com"/>
        <meta property="og:site_name" content="PHP: The Right Way"/>
        <meta property="og:type" content="website"/>
        <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
        <link rel="icon" href="../images/favicon.png" type="image/png"/>
        <link rel="stylesheet" href="../styles/syntax.css">
        <link rel="stylesheet" href="../styles/css.css">
        <base href="">
        <style>
        
        </style>
    </head>
    <body>
        <header class="site-header">
            <h1><a href="http://laravel-china.github.io/">PHP <em>The Right Way</em></a></h1>
            <div class="build-date">最后更新于: 2017-03-16 03:10:55 +0000 由 <a href="https://laravel-china.org/" target="_blank" >Laravel China 社区</a> 维护</div>
            <div class="share">
                <a class="btn-share" href="http://service.weibo.com/share/share.php?url=http://laravel-china.github.io/php-the-right-way&title=PHP+The+Right+Way+PHP+之道" target="_blank">分享到微博</a>
                <br>
                 <a class="btn-share" href="https://laravel-china.org/" target="_blank" style="margin-top: 13px;">❤ Laravel China 社区 ❤</a> 
            </div>
            <a class="fork-me" href="https://github.com/codeguy/php-the-right-way" target="_blank"></a>
        </header>

        <div class="site-content">
            <h1 id="设计模式">设计模式</h1>

<p>构建一个 web 应用或工程有无数种方式，同样，架构的方式也有无数种。但是我们建议遵循一些常见的模式，因为这会让你的代码更容易管理，可读性更高。</p>

<ul>
  <li><a href="https://en.wikipedia.org/wiki/Architectural_pattern">维基百科——架构模式</a></li>
  <li><a href="https://en.wikipedia.org/wiki/Software_design_pattern">维基百科——软件设计模式</a></li>
  <li><a href="http://designpatternsphp.readthedocs.io/en/latest/">一些设计模式的例子</a></li>
</ul>

<h2 id="工厂模式">工厂模式</h2>

<p>最常用的设计模式就是工厂模式。在这个模式下，需要一个用来创建你需要的对象的类。考虑下面的工厂模式的例子：</p>

<figure class="highlight"><pre><code class="language-php" data-lang="php"><span class="cp">&lt;?php</span>
<span class="k">class</span> <span class="nc">Automobile</span>
<span class="p">{</span>
    <span class="k">private</span> <span class="nv">$vehicleMake</span><span class="p">;</span>
    <span class="k">private</span> <span class="nv">$vehicleModel</span><span class="p">;</span>

    <span class="k">public</span> <span class="k">function</span> <span class="nf">__construct</span><span class="p">(</span><span class="nv">$make</span><span class="p">,</span> <span class="nv">$model</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">vehicleMake</span> <span class="o">=</span> <span class="nv">$make</span><span class="p">;</span>
        <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">vehicleModel</span> <span class="o">=</span> <span class="nv">$model</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="k">function</span> <span class="nf">getMakeAndModel</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="k">return</span> <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">vehicleMake</span> <span class="o">.</span> <span class="s1">' '</span> <span class="o">.</span> <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">vehicleModel</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">class</span> <span class="nc">AutomobileFactory</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="k">static</span> <span class="k">function</span> <span class="nf">create</span><span class="p">(</span><span class="nv">$make</span><span class="p">,</span> <span class="nv">$model</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="k">return</span> <span class="k">new</span> <span class="nx">Automobile</span><span class="p">(</span><span class="nv">$make</span><span class="p">,</span> <span class="nv">$model</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="c1">// 用工厂的 create 方法创建 Automobile 对象
</span><span class="nv">$veyron</span> <span class="o">=</span> <span class="nx">AutomobileFactory</span><span class="o">::</span><span class="na">create</span><span class="p">(</span><span class="s1">'Bugatti'</span><span class="p">,</span> <span class="s1">'Veyron'</span><span class="p">);</span>

<span class="nb">print_r</span><span class="p">(</span><span class="nv">$veyron</span><span class="o">-&gt;</span><span class="na">getMakeAndModel</span><span class="p">());</span> <span class="o">//</span> <span class="nx">outputs</span> <span class="s2">"Bugatti Veyron"</span></code></pre></figure>

<p>上面的代码用来一个工厂来创建 Automobile 对象。用这种方式创建对象有两个好处：
首先，如果你后续需要更改，重命名或替换 Automobile 类，你只需要更改工厂类中的代码，而不是在每一个用到 Automobile 类的地方修改；
其次，如果创建对象的过程很复杂，你也只需要在工厂类中写，而不是在每个创建实例的地方重复地写。</p>

<p>当然，用工厂模式并不总是必要（或者明智）。上面的示例代码很简单，在实践中，工厂类中会被加入一些不必要的复杂性。
如果你是在做一个很大很复杂的项目，使用工厂模式将会给你省去很多麻烦。</p>

<ul>
  <li><a href="https://en.wikipedia.org/wiki/Factory_pattern">维基百科——工厂模式</a></li>
</ul>

<h2 id="单例模式">单例模式</h2>

<p>我们设计 web 应用时，我们经常需要取得某个类的唯一实例，单例模式就帮我们解决了这个问题。</p>

<figure class="highlight"><pre><code class="language-php" data-lang="php"><span class="cp">&lt;?php</span>
<span class="k">class</span> <span class="nc">Singleton</span>
<span class="p">{</span>
    <span class="sd">/**
     * @var 这个类的 *单例*
     */</span>
    <span class="k">private</span> <span class="k">static</span> <span class="nv">$instance</span><span class="p">;</span>
    
    <span class="sd">/**
     * 返回这个类的 *单例*
     *
     * @return Singleton The *Singleton* instance.
     */</span>
    <span class="k">public</span> <span class="k">static</span> <span class="k">function</span> <span class="nf">getInstance</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="k">if</span> <span class="p">(</span><span class="kc">null</span> <span class="o">===</span> <span class="k">static</span><span class="o">::</span><span class="nv">$instance</span><span class="p">)</span> <span class="p">{</span>
            <span class="k">static</span><span class="o">::</span><span class="nv">$instance</span> <span class="o">=</span> <span class="k">new</span> <span class="k">static</span><span class="p">();</span>
        <span class="p">}</span>
        
        <span class="k">return</span> <span class="k">static</span><span class="o">::</span><span class="nv">$instance</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="sd">/**
     * 把构造函数声明为 protected，防止用 `new` 操作符在这个类之外创建新的实例
     */</span>
    <span class="k">protected</span> <span class="k">function</span> <span class="nf">__construct</span><span class="p">()</span>
    <span class="p">{</span>
    <span class="p">}</span>

    <span class="sd">/**
     * 把 clone 方法声明为 private，防止克隆单例
     *
     * @return void
     */</span>
    <span class="k">private</span> <span class="k">function</span> <span class="nf">__clone</span><span class="p">()</span>
    <span class="p">{</span>
    <span class="p">}</span>

    <span class="sd">/**
     * 把反序列化方法声明为 private，防止反序列化单例
     *
     * @return void
     */</span>
    <span class="k">private</span> <span class="k">function</span> <span class="nf">__wakeup</span><span class="p">()</span>
    <span class="p">{</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">class</span> <span class="nc">SingletonChild</span> <span class="k">extends</span> <span class="nx">Singleton</span>
<span class="p">{</span>
<span class="p">}</span>

<span class="nv">$obj</span> <span class="o">=</span> <span class="nx">Singleton</span><span class="o">::</span><span class="na">getInstance</span><span class="p">();</span>
<span class="nb">var_dump</span><span class="p">(</span><span class="nv">$obj</span> <span class="o">===</span> <span class="nx">Singleton</span><span class="o">::</span><span class="na">getInstance</span><span class="p">());</span>             <span class="c1">// bool(true)
</span>
<span class="nv">$anotherObj</span> <span class="o">=</span> <span class="nx">SingletonChild</span><span class="o">::</span><span class="na">getInstance</span><span class="p">();</span>
<span class="nb">var_dump</span><span class="p">(</span><span class="nv">$anotherObj</span> <span class="o">===</span> <span class="nx">Singleton</span><span class="o">::</span><span class="na">getInstance</span><span class="p">());</span>      <span class="c1">// bool(false)
</span>
<span class="nb">var_dump</span><span class="p">(</span><span class="nv">$anotherObj</span> <span class="o">===</span> <span class="nx">SingletonChild</span><span class="o">::</span><span class="na">getInstance</span><span class="p">());</span> <span class="o">//</span> <span class="nx">bool</span><span class="p">(</span><span class="kc">true</span><span class="p">)</span></code></pre></figure>

<p>上面的代码用<a href="http://php.net/language.variables.scope#language.variables.scope.static"><strong>静态</strong>变量</a> 实现了单例模式和创建单例的静态方法 <code class="highlighter-rouge">getInstance()</code>.
请注意以下几点：</p>

<ul>
  <li>构造函数 <a href="http://php.net/language.oop5.decon#object.construct"><code class="highlighter-rouge">__construct()</code></a> 被声明为 protected 是为了防止用 <code class="highlighter-rouge">new</code> 操作符在这个类之外创建新的实例。</li>
  <li>魔术方法 <a href="http://php.net/language.oop5.cloning#object.clone"><code class="highlighter-rouge">__clone()</code></a> 被声明为 private 是为了防止用 <a href="http://php.net/language.oop5.cloning"><code class="highlighter-rouge">clone</code></a> 操作符克隆出新的实例.</li>
  <li>魔术方法 <a href="http://php.net/language.oop5.magic#object.wakeup"><code class="highlighter-rouge">__wakeup()</code></a> 被声明为 private 是为了防止通过全局函数 <a href="http://php.net/function.unserialize"><code class="highlighter-rouge">unserialize()</code></a> 反序列化这个类的实例。</li>
  <li>新的实例是用过静态方法 <code class="highlighter-rouge">getInstance()</code> 使用<a href="http://php.net/language.oop5.late-static-bindings">后期静态绑定</a>生成的。这允许我们对 <code class="highlighter-rouge">Singleton</code> 类进行继承，并且在取得 <code class="highlighter-rouge">SingletonChild</code> 的单例时不会出现问题。</li>
</ul>

<p>单例模式是非常有用的，特别是我们需要确保在整个请求的声明周期内只有一个实例存在。
典型的应用场景是，当我们有一个全局的对象（比如配置类）或一个共享的资源（比如事件队列）时。</p>

<p>你应该非常小心地使用单例模式，因为它非常自然地引入了全局状态到你的应用中，降低了可测试性。
在大多数情况下，依赖注入可以（并且应该）代替单例类。
使用依赖注入意味着我们不会在设计应用时引入不必要的耦合，因为对象使用共享的或全局的资源，不再需要耦合具体的类。</p>

<ul>
  <li><a href="https://en.wikipedia.org/wiki/Singleton_pattern">维基百科——单例模式</a></li>
</ul>

<h2 id="策略模式">策略模式</h2>

<p>使用策略模式，你可以把一族不同的算法（业务）封装到不同的类中，使 client 类可以在不知道具体实现的情况下选择实例化其中一个算法。策略模式有几种不同的变体，最简单的是下面这种：</p>

<p>第一段代码展示了一族输出算法，分别具体实现了 <code class="highlighter-rouge">OutputInterface</code> 的 <code class="highlighter-rouge">load</code> 方法，返回序列化结果，json 和数组：</p>

<figure class="highlight"><pre><code class="language-php" data-lang="php"><span class="cp">&lt;?php</span>

<span class="k">interface</span> <span class="nx">OutputInterface</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="k">function</span> <span class="nf">load</span><span class="p">();</span>
<span class="p">}</span>

<span class="k">class</span> <span class="nc">SerializedArrayOutput</span> <span class="k">implements</span> <span class="nx">OutputInterface</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="k">function</span> <span class="nf">load</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="k">return</span> <span class="nb">serialize</span><span class="p">(</span><span class="nv">$arrayOfData</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">class</span> <span class="nc">JsonStringOutput</span> <span class="k">implements</span> <span class="nx">OutputInterface</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="k">function</span> <span class="nf">load</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="k">return</span> <span class="nb">json_encode</span><span class="p">(</span><span class="nv">$arrayOfData</span><span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>

<span class="k">class</span> <span class="nc">ArrayOutput</span> <span class="k">implements</span> <span class="nx">OutputInterface</span>
<span class="p">{</span>
    <span class="k">public</span> <span class="k">function</span> <span class="nf">load</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="k">return</span> <span class="nv">$arrayOfData</span><span class="p">;</span>
    <span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<p>通过像上面这样把不同类型的输出算法封装起来，其他的开发者可以很容易地在不影响 client 代码的情况下添加新的输出类型。</p>

<p>每个具体的输出类实现了 <code class="highlighter-rouge">OutputInterface</code> —— 这有两个目的，第一是它提供了一个所有输出类都必须遵守的契约，第二，你将会在本文后面的部分看到，通过实现公共的接口，你可以利用<a href="http://php.net/language.oop5.typehinting">类型约束</a>保证 client 中使用的输出类必须是实现了 <code class="highlighter-rouge">OutputInterface</code> 的类。</p>

<p>接下来的一小段代码展示了一个 client 类如何使用其中一个输出算法，并可以在运行时根据需要选用不同的算法。</p>

<figure class="highlight"><pre><code class="language-php" data-lang="php"><span class="cp">&lt;?php</span>
<span class="k">class</span> <span class="nc">SomeClient</span>
<span class="p">{</span>
    <span class="k">private</span> <span class="nv">$output</span><span class="p">;</span>

    <span class="k">public</span> <span class="k">function</span> <span class="nf">setOutput</span><span class="p">(</span><span class="nx">OutputInterface</span> <span class="nv">$outputType</span><span class="p">)</span>
    <span class="p">{</span>
        <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">output</span> <span class="o">=</span> <span class="nv">$outputType</span><span class="p">;</span>
    <span class="p">}</span>

    <span class="k">public</span> <span class="k">function</span> <span class="nf">loadOutput</span><span class="p">()</span>
    <span class="p">{</span>
        <span class="k">return</span> <span class="nv">$this</span><span class="o">-&gt;</span><span class="na">output</span><span class="o">-&gt;</span><span class="na">load</span><span class="p">();</span>
    <span class="p">}</span>
<span class="p">}</span></code></pre></figure>

<p>上面的 <code class="highlighter-rouge">client</code>类有一个必须在运行时设置的私有属性，并且是“OutputInterface”类型的。
一旦这个属性被设置为具体的实例（三个输出类中之一的实例），并且 <code class="highlighter-rouge">loadOutput</code> 方法被调用，那么它的 <code class="highlighter-rouge">load</code> 方法就会被调用，返回回序列化结果或 json 或数组。</p>

<figure class="highlight"><pre><code class="language-php" data-lang="php"><span class="cp">&lt;?php</span>
<span class="nv">$client</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">SomeClient</span><span class="p">();</span>

<span class="c1">// Want an array?
</span><span class="nv">$client</span><span class="o">-&gt;</span><span class="na">setOutput</span><span class="p">(</span><span class="k">new</span> <span class="nx">ArrayOutput</span><span class="p">());</span>
<span class="nv">$data</span> <span class="o">=</span> <span class="nv">$client</span><span class="o">-&gt;</span><span class="na">loadOutput</span><span class="p">();</span>

<span class="c1">// Want some JSON?
</span><span class="nv">$client</span><span class="o">-&gt;</span><span class="na">setOutput</span><span class="p">(</span><span class="k">new</span> <span class="nx">JsonStringOutput</span><span class="p">());</span>
<span class="nv">$data</span> <span class="o">=</span> <span class="nv">$client</span><span class="o">-&gt;</span><span class="na">loadOutput</span><span class="p">();</span></code></pre></figure>

<ul>
  <li><a href="http://en.wikipedia.org/wiki/Strategy_pattern">维基百科——策略模式</a></li>
</ul>

<h2 id="前端控制器模式">前端控制器模式</h2>

<p>前端控制器模式就是给你的 web 应用程序设置单一的入口（比如 index.php），用来集中处理所有请求的机制。
它的职责是载入所有依赖，处理请求，并发送响应给浏览器。前端控制器模式对整个架构是有益的，因为它鼓励模块化代码，并给了你一个单入口，可以写一些每个请求都需要跑的代码（比如输入数据的过滤）。</p>

<ul>
  <li><a href="https://en.wikipedia.org/wiki/Front_Controller_pattern">维基百科——前端控制器模式</a></li>
</ul>

<h2 id="模型-视图-控制器mvc">模型-视图-控制器（MVC）</h2>

<p>模型-视图-控制器 (MVC) 模式还有和它相关的 HMVC、HVVM 让你根据逻辑对象的不同作用去解耦。
模型用来作为数据访问层，并以应用中通用的格式返回数据。
控制器处理请求，处理从模型层返回的数据，并载入视图，发送响应。
视图用来展示需要在响应中使用的模板（markup, xml 等等）。</p>

<p>MVC 是在各个<a href="https://github.com/codeguy/php-the-right-way/wiki/Frameworks">PHP 框架</a>中使用最普遍的架构模式。</p>

<p>学习更多 MVC 及其相关架构模式的链接：</p>

<ul>
  <li><a href="https://en.wikipedia.org/wiki/Model%E2%80%93View%E2%80%93Controller">MVC</a></li>
  <li><a href="https://en.wikipedia.org/wiki/Hierarchical_model%E2%80%93view%E2%80%93controller">HMVC</a></li>
  <li><a href="https://en.wikipedia.org/wiki/Model_View_ViewModel">MVVM</a></li>
</ul>

<p>（译者注：MVC 属架构模式，和设计模式是不同层级的概念，请不要因为本文把它列在“设计模式”下而混淆。）</p>

        </div>

        <footer class="site-footer" id="credits">
             <h2>中文版本</h2>  
            <ul>
                <li><a href="https://github.com/summerblue" target="_blank">Summer</a></li>
                <li><a href="https://laravel-china.org/" target="_blank">Laravel China 社区</a></li>
                <li><a href="https://github.com/laravel-china/php-the-right-way/graphs/contributors" target="_blank">项目贡献者</a></li>
            </ul>
            <h2>原始版本创建和维护</h2>
            <ul>
                <li><a href="http://joshlockhart.com" target="_blank">Josh Lockhart</a></li>
                <li><a href="http://philsturgeon.co.uk/" target="_blank">Phil Sturgeon</a></li>
                <li><a href="https://github.com/codeguy/php-the-right-way/graphs/contributors" target="_blank">Project Contributors</a></li>
            </ul>
            <div class="license">
                <a class="cc-badge" rel="license" href="http://creativecommons.org/licenses/by-nc-sa/3.0/"></a>
                <br />
                <span xmlns:dct="http://purl.org/dc/terms/" href="http://purl.org/dc/dcmitype/Text" property="dct:title" rel="dct:type">PHP: The Right Way</span> by <a xmlns:cc="http://creativecommons.org/ns#" href="http://joshlockhart.com" property="cc:attributionName" rel="cc:attributionURL">Josh Lockhart</a>
                <br/>
                is licensed under a <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/3.0/">Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License</a>.<br />Based on a work at <a xmlns:dct="http://purl.org/dc/terms/" href="http://www.phptherightway.com" rel="dct:source">www.phptherightway.com</a>.
            </div>
        </footer>

    </body>
</html>
